// tge.rs
// copyright zipxing@hotmail.com 2022~2023

use crate::{
    LOGO_FRAME,
    render::{
        adapter::Adapter,
        buffer::Buffer,
        sprite::Sprites,
        style::{Color, Modifier, ModifierDiff},
        image::to_error,
    },
    util::Rect,
};
use crossterm::{
    cursor::{Hide, MoveTo, Show},
    event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
    execute, queue,
    style::{
        Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
        SetForegroundColor,
    },
    terminal::{
        self, disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
    },
};
use std::io::{self, Write};
use std::time::Duration;

pub struct CrosstermAdapter {
    pub writer: Box<dyn Write>,
    // 按cell的宽度高度
    pub cell_w: u16,
    pub cell_h: u16,
}

impl CrosstermAdapter {
    pub fn new() -> Self {
        let stdout = io::stdout();
        Self {
            writer: Box::new(stdout),
            cell_w: 0,
            cell_h: 0,
        }
    }
}

impl Adapter for CrosstermAdapter {
    fn init(&mut self, w: u16, h: u16, _rx: f32, _ry: f32, _s: String) {
        self.cell_w = w;
        self.cell_h = h;
        // 检查终端尺寸，如果小于绘制需要的尺寸，则警告退出
        let (width, height) = terminal::size().unwrap();
        if w > width || h > height {
            self.reset();
            panic!(
                "\n\nTerminal too small!\n\
                Render required size:(width: {}, height: {})\n\
                Terminal size:(width : {}, height: {}).\n\n",
                w, h, width, height
            );
        }
        enable_raw_mode().unwrap();
        let mut stdout = io::stdout();
        execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
    }

    fn reset(&mut self) {
        disable_raw_mode().unwrap();
        execute!(self.writer, LeaveAlternateScreen, DisableMouseCapture).unwrap();
        self.show_cursor().unwrap();
    }

    fn cell_width(&self) -> f32 {
        0.0
    }

    fn cell_height(&self) -> f32 {
        0.0
    }

    fn size(&self) -> Rect {
        Rect::new(0, 0, self.cell_w, self.cell_h)
    }

    fn hide_cursor(&mut self) -> Result<(), String> {
        to_error(execute!(self.writer, Hide))?;
        Ok(())
    }

    fn show_cursor(&mut self) -> Result<(), String> {
        to_error(execute!(self.writer, Show))?;
        Ok(())
    }

    fn get_cursor(&mut self) -> Result<(u16, u16), String> {
        crossterm::cursor::position().map_err(|e| e.to_string())
    }

    fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), String> {
        to_error(execute!(self.writer, MoveTo(x, y)))
    }

    fn poll_event(&mut self, timeout: Duration, es: &mut Vec<Event>) -> bool {
        if crossterm::event::poll(timeout).unwrap() {
            let e = crossterm::event::read().unwrap();
            es.push(e);
            match e {
                Event::Key(key) => match key.code {
                    KeyCode::Char('q') => {
                        return true;
                    }
                    _ => {}
                },
                _ => {}
            }
        }
        false
    }

    fn render_buffer(
        &mut self,
        current_buffer: &Buffer,
        previous_buffer: &Buffer,
        _pix: &mut Sprites,
        stage: u32,
    ) -> Result<(), String> {
        if stage <= LOGO_FRAME {
            to_error(queue!(self.writer, MoveTo(3, 3)))?;
            to_error(queue!(self.writer, Print("tge.rs")))?;
            if stage == LOGO_FRAME {
                to_error(queue!(self.writer, MoveTo(3, 3)))?;
                to_error(queue!(self.writer, Print("      ")))?;
            }
            return Ok(());
        }
        let updates = previous_buffer.diff(current_buffer);

        let mut fg = Color::Reset;
        let mut bg = Color::Reset;
        let mut modifier = Modifier::empty();
        let mut last_pos: Option<(u16, u16)> = None;
        for (x, y, cell) in updates {
            // Move the cursor if the previous location was not (x - 1, y)
            if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
                to_error(queue!(self.writer, MoveTo(x, y)))?;
            }
            last_pos = Some((x, y));
            if cell.modifier != modifier {
                let diff = ModifierDiff {
                    from: modifier,
                    to: cell.modifier,
                };
                to_error(diff.queue(&mut self.writer))?;
                modifier = cell.modifier;
            }
            if cell.fg != fg {
                let color = CColor::from(cell.fg);
                to_error(queue!(self.writer, SetForegroundColor(color)))?;
                fg = cell.fg;
            }
            if cell.bg != bg {
                let color = CColor::from(cell.bg);
                to_error(queue!(self.writer, SetBackgroundColor(color)))?;
                bg = cell.bg;
            }

            to_error(queue!(self.writer, Print(&cell.symbol)))?;
        }
        to_error(queue!(
            self.writer,
            SetForegroundColor(CColor::Reset),
            SetBackgroundColor(CColor::Reset),
            SetAttribute(CAttribute::Reset)
        ))
    }
}
